#pragma once
#ifndef _KH_ENGINE_WORLD_H
#define _KH_ENGINE_WORLD_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Net.h>
#include <map>

namespace kharlia { namespace engine {

class CLevel;
class CEngine;
class CActor;
class CWorld;
class CPlayer;
class APlayerController;

enum ENetMode {
    NM_STANDALONE,
    NM_DEDICATED_SERVER,
    NM_LISTEN_SERVER,
    NM_CLIENT
};

//bool WriteGUID(RN::BitStream* out, object& guid);
//bool ReadGUID(RN::BitStream* in, object&);

class FReplicaManager;

class KH_ENGINE_DECLSPEC FRMConnection: public rn::Connection_RM3 {
    shared_ptr<CPlayer> mPlayer;

public:

    FRMConnection(
        rn::SystemAddress addr,
        rn::RakNetGUID guid
    ):
        Connection_RM3(addr, guid),
        mPlayer() {}

    virtual rn::Replica3* AllocReplica(rn::BitStream*, rn::ReplicaManager3*);

    KH_DEF_GETSET(const shared_ptr<CPlayer>&, Player)

    friend class FReplicaManager;
};

class KH_ENGINE_DECLSPEC FReplicaManager: public rn::ReplicaManager3 {
    CWorld* mWorld;

public:
    FReplicaManager(CWorld* world): mWorld(world) {}
    
    CWorld* GetWorldPtr() const { return mWorld; }
    virtual rn::Connection_RM3* AllocConnection(const rn::SystemAddress&, rn::RakNetGUID) const;
    virtual void DeallocConnection(rn::Connection_RM3*) const;
    virtual void UpdateNow();
    virtual void Update() {} // do nothing

    friend class FRMConnection;
};

class KH_ENGINE_DECLSPEC CGame: public core::CObject {
    shared_ptr<CWorld> mWorld;
    bool mPaused;
    bool mStarted;

    void _OnStartGame(shared_ptr<CWorld> world);
    void _OnStopGame();

    KH_DECL_GC()

public:
    CGame(PyObject* self);
    virtual ~CGame() {}

    virtual void Tick(float deltaTime);

    kh_pycall void OnStartGame(shared_ptr<CWorld> world);
    kh_pycall void OnStopGame();
    kh_pycall shared_ptr<APlayerController> OnLogin();

    KH_DEF_GET(const shared_ptr<CWorld>&, World)
    KH_DEF_GET(bool, Paused)
    KH_DEF_GET(bool, Started)

    friend void _InitWorldClasses();
    friend void _InitWorldBindings();
};

inline void CGame::OnStartGame(shared_ptr<CWorld> world) {
    call_method<void>(mSelf, "OnStartGame", world);
}

inline void CGame::OnStopGame() {
    call_method<void>(mSelf, "OnStopGame");
}

inline shared_ptr<APlayerController> CGame::OnLogin() {
    return call_method<shared_ptr<APlayerController>>(mSelf, "OnLogin");
}

// TODO: Have world instance create a default level for rep info actors.

class KH_ENGINE_DECLSPEC CWorld: public core::CObject {
public:
    typedef std::vector<CLevel*> LevelPtrsType;

private:
    shared_ptr<core::FLogger> mLogger;
    bool mTicking;
    bool mInited;
    shared_ptr<CEngine> mEngine;
    shared_ptr<CGame> mGame;
    object mGameClass;
    dict mLevels;
    dict mLevelHashMap;
    LevelPtrsType mLevelPtrs;
    shared_ptr<CLevel> mDefaultLevel;
    list mPlayers;

    // Network
    rn::RakPeerInterface* mNetPeer;
    bool mNetIsEnabled;
    bool mNetIsServer;
    bool mNetIsClient; // True if we're the client
    bool mNetIsListening;
    bool mNetIsConnecting;
    std::vector<rn::RakNetGUID> mNetClientConnections;
    rn::RakNetGUID mNetServerConnection;
    FReplicaManager* mNetReplicaManager;
    rn::NetworkIDManager mNetIDManager;
    uint16 mNetTickRate;
    uint16 mNetRepRate;
    float mNetTickElapsed;
    float mNetRepElapsed;

    static object msCreateGUIDFunc;
    static object msGUIDClass;
    static object msLevelClass;

    KH_DECL_GC()

public:
    CWorld(PyObject* self);
    virtual ~CWorld();

    void Init(shared_ptr<CEngine> engine);
    void Exit();
    void Tick(float deltaTime);

    shared_ptr<CLevel> CreateLevel(object guid, unicode name, const FSizeI& size);
    shared_ptr<CLevel> CreateDefaultLevel();
    void DestroyLevel(shared_ptr<CLevel>);
    void DestroyLevels();
    void StartGame();
    void StopGame();

    shared_ptr<CLevel> GetLevel(object name);
    //shared_ptr<CActor> GetActor(object levelName, object actorName);
    ENetMode GetNetMode();

    // Networking

    void NetListen();
    void NetConnect(str host, uint16 port);
    void NetExit();
    void NetTickReceive(float deltaTime);
    void NetTickSend(float deltaTime);
    void NetSendDebugMessage(str msg);
    void NetReference(rn::Replica3* actor);
    void NetDereference(rn::Replica3* actor);
    void NetSendActorRPC(bool reliable, shared_ptr<CActor> actor, str funcname, tuple args);
    void NetOnReceiveActorRPC(rn::BitStream* bs);
    void OnClientConnect(rn::RakNetGUID guid, rn::SystemAddress addr);
    
    KH_DEF_GET(bool, Inited)
    KH_DEF_GET(const shared_ptr<core::FLogger>&, Logger)
    KH_DEF_GET(const shared_ptr<CEngine>&, Engine)
    KH_DEF_GET(const shared_ptr<CGame>&, Game)
    void SetGame(const shared_ptr<CGame>& game);
    KH_DEF_GETSET(const object&, GameClass)
    KH_DEF_GET(const dict&, Levels)
    KH_DEF_GET(const dict&, LevelHashMap)
    KH_DEF_GET(const LevelPtrsType&, LevelPtrs)
    KH_DEF_GET(const shared_ptr<CLevel>&, DefaultLevel)
    KH_DEF_GET(bool, NetIsEnabled)
    KH_DEF_GET(bool, NetIsServer)
    KH_DEF_GET(bool, NetIsClient)
    KH_DEF_GETSET(uint16, NetTickRate)
    KH_DEF_GETSET(uint16, NetRepRate)
    bool GetGameStarted() const;

    static object CreateGUID();
    static void SetLevelClass(object cls);

    friend void _InitWorldClasses();
    friend void _InitWorldBindings();
    friend void _ExitWorldClasses();
    friend class CEngine;
    friend class CLevel;
};

inline void CWorld::SetGame(const shared_ptr<CGame>& game) {
    mGame = game;
}

inline bool CWorld::GetGameStarted() const {
    return mGame? mGame->GetStarted(): false;
}

inline void CWorld::NetReference(rn::Replica3* actor) {
    if (mNetReplicaManager)
        mNetReplicaManager->Reference(actor);
}

inline void CWorld::NetDereference(rn::Replica3* actor) {
    if (mNetReplicaManager)
        mNetReplicaManager->Dereference(actor);
}

}} // namespace Kharlia

KH_HAS_BACK_REFERENCE(kharlia::engine::CGame)
KH_HAS_BACK_REFERENCE(kharlia::engine::CWorld)

#endif